using System;
using System.Collections.Generic;
using System.Linq;
using System.Diagnostics;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;


namespace _2DGame
{
    /// <summary>
    /// This is a game component that implements IUpdateable.
    /// </summary>
    public class Ship : GameObject
    {
        Vector2 lastPosition;
        float turretRotation;

        float sleekness;    // Default is 1.0
        float viscocity;
        float basedrag;     // The magical multiplier.
        float drag;         // Sleekness * Viscosity.  Modifies the velocity every frame.
        float enginepower;  // Power of the engines!
        float mass;         // The total mass of the ship

        Player owner;

        GamePadState previousGamePadState;  // Stash the gamepad state here for debouncing and autorepeats.

        float firingRate;           // Time (in seconds) between automatic firing shots.
        float autoRepeatTotal;      // Time (in seconds) that the firing button has been held down.
        float maxHullPoints;
        float currentHullPoints;
        float maxShieldPoints;
        float currentShieldPoints;

        int numMissiles;
        int maxMissiles = 3;

        float shieldRegenRate;      // Time (in seconds) required to regen one point of shield.
        float shieldRegenTotal;     // Time (in seconds) since the last regen tick.

        float structuralHardness;   // If physical damage is below this threshold, no damage is taken
        float structuralStrength;   // Determines how much damage you take when crashing into things

        bool touchingSafeZone = false;
        float safeTimer;
        float initialSafeTime = 2.0f;
        float safeTickTime = 0.25f;
        
        // Maximum hit or hull points.  Run out of these and die. 
        public float MaxHullPoints 
        { 
            get { return maxHullPoints; }
            set { maxHullPoints = value; }
        }
        public float CurrentHullPoints 
        {
            get { return currentHullPoints; }
            set { currentHullPoints = value; }
        }
        // Maximum shield points.  These regenerate over time.
        public float MaxShieldPoints 
        { 
            get { return maxShieldPoints; }
            set { maxShieldPoints = value; }
        }
        public float CurrentShieldPoints 
        { 
            get { return currentShieldPoints; }
            set { currentShieldPoints = value; }
        }

        public int NumMissiles
        {
            get { return numMissiles; }
            set { numMissiles = value; }
        }
        public float Mass
        {
            get { return mass; }
        }

        public Ship(String filename, Player fromOwner) : base(filename, SpriteBlendMode.AlphaBlend)
        {
            canBeCollidedWith = true;

            // TODO: Construct any child components here
            sleekness = 1.0f;
            viscocity = 1.0f;
            basedrag = 0.01f;
            drag = sleekness * viscocity * basedrag;
            autoRepeatTotal = 0.0f;

            mass = 1.0f;

            enginepower = 8.0f;

            firingRate = 0.1f;

            maxHullPoints = 10.0f;
            currentHullPoints = maxHullPoints;      // Set up the ship at full Hull Points.

            maxShieldPoints = 10.0f;
            currentShieldPoints = maxShieldPoints;  // Ship has fully regenerated shields at the start of the game.

            shieldRegenRate = 1.0f;
            shieldRegenTotal = 0.0f;

            structuralHardness = 0.1f;
            structuralStrength = 25.0f;

            numMissiles = maxMissiles;

            turretRotation = 0.0f;

            safeTimer = initialSafeTime;

            owner = fromOwner;
        }

        /// <summary>
        /// Allows the game component to update itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        public override void Update(GameTime gameTime)
        {

            GamePadState gamePadState = GamePad.GetState(owner.Index);

            float thrustValue = 0.0f;

            if (gamePadState.ThumbSticks.Left.Length() > 0.2f)
            {
                float desiredAngle = (float)Math.Atan2(gamePadState.ThumbSticks.Left.X, gamePadState.ThumbSticks.Left.Y);
                float angularDifference = desiredAngle - rotation;

                if (angularDifference > Math.PI)
                    angularDifference -= (float)(Math.PI * 2.0f);
                if (angularDifference < -Math.PI)
                    angularDifference += (float)(Math.PI * 2.0f);

                // Take the rotation from the left thumbstick.
                angularVelocity = angularDifference * 3.0f;

                if (Math.Abs(angularDifference) < (Math.PI * 0.5f))
                {
                    float thrustFalloff = 1.0f - (Math.Abs(angularDifference) / (float)(Math.PI * 0.5f));
                    thrustFalloff = (float)Math.Sqrt(thrustFalloff);
                    thrustValue = thrustFalloff * gamePadState.ThumbSticks.Left.Length();
                }

                velocity += new Vector2((float)(Math.Cos(rotation - MathHelper.PiOver2) * thrustValue * enginepower),
                                       (float)(Math.Sin(rotation - MathHelper.PiOver2) * thrustValue * enginepower));
            }
            else
            {
                angularVelocity *= 0.3f;
            }

            if (gamePadState.Buttons.A == ButtonState.Pressed)
            {
            }

            // GRAVITY!
            velocity += Resources.World.Gravity;

            // DRAG
            velocity -= velocity * drag;

            lastPosition = position;

            base.Update(gameTime);

            // REGENERATE SHIELDS
            shieldRegenTotal += (float) gameTime.ElapsedGameTime.TotalSeconds ;
            if (shieldRegenTotal >= shieldRegenRate)
            {
                // Regenerate the shield by a point.
                currentShieldPoints += 1.0f;
                if (currentShieldPoints > maxShieldPoints)
                { currentShieldPoints = maxShieldPoints; }
                shieldRegenTotal = 0.0f;
            }

            if (touchingSafeZone)
            {
                safeTimer -= (float)gameTime.ElapsedGameTime.TotalSeconds;
                if (safeTimer < 0.0f)
                {
                    currentShieldPoints += 1.0f;
                    if (currentShieldPoints > maxShieldPoints)
                        currentShieldPoints = maxShieldPoints;
                    currentHullPoints += 1.0f;
                    if (currentHullPoints > maxHullPoints)
                        currentHullPoints = maxHullPoints;
                    safeTimer += safeTickTime;
                }
            }
            else
            {
                safeTimer = initialSafeTime;
            }

            // FIRING THE CANNON
            if (gamePadState.ThumbSticks.Right.Length() > 0.5f)
            {
                turretRotation = (float)Math.Atan2(gamePadState.ThumbSticks.Right.X, gamePadState.ThumbSticks.Right.Y);
                autoRepeatTotal += (float)gameTime.ElapsedGameTime.TotalSeconds;

                if ((autoRepeatTotal >= firingRate) || (previousGamePadState.ThumbSticks.Right.Length() <= 0.5f))
                {
                    if (autoRepeatTotal >= firingRate)
                    {
                        autoRepeatTotal = 0.0f;
                    }

                    // Work out the starting position of the bullet.  (We'll have to vary this with gun type later)
                    Vector2 pointing;
                    float distanceToGun;
                    Vector2 bulletOffset;
                    Vector2 startVel;

                    pointing = new Vector2((float)(Math.Cos(turretRotation - MathHelper.PiOver2)),
                                           (float)(Math.Sin(turretRotation - MathHelper.PiOver2)));
                    distanceToGun = Texture.Height * 0.4f;
                    bulletOffset = position + (pointing * distanceToGun);
                    startVel = (pointing * 500.0f) + velocity;

                    Bullet newBullet = new Bullet("bullet_small", owner, bulletOffset, startVel);
                    newBullet.IgnoreUntilSeparated(this);
                    Resources.World.AddObject(newBullet);
                }
            }

            // FIRING A MISSILE
            if (gamePadState.IsButtonDown(Buttons.RightTrigger) == true && previousGamePadState.IsButtonDown(Buttons.RightTrigger) == false)
            {
                if (numMissiles > 0)
                {
                    // Work out the starting position of the bullet.  (We'll have to vary this with gun type later)
                    Vector2 pointing;
                    float distanceToGun;
                    Vector2 missileOffset;
                    Vector2 startVel;

                    pointing = new Vector2((float)(Math.Cos(turretRotation - MathHelper.PiOver2)),
                                           (float)(Math.Sin(turretRotation - MathHelper.PiOver2)));
                    distanceToGun = Texture.Height * 0.4f;
                    missileOffset = position + (pointing * distanceToGun);
                    startVel = (pointing * 250.0f) + velocity;

                    Missile newMissile = new Missile("missile", owner, missileOffset, startVel);
                    newMissile.IgnoreUntilSeparated(this);
                    Resources.World.AddObject(newMissile);

                    --numMissiles;
                }
            }


            // Do this after base update, so particles appear after the ship has moved
            if (thrustValue > 0.0f)
                AddThrustParticles(thrustValue);

            previousGamePadState = gamePadState;
        }

        public override void Draw()
        {
            base.Draw();
        }

        public override void TouchMap(List<Map.CollisionInfo> collisions)
        {
            int numSolidHits = 0;
            int numSafeHits = 0;
            int numDestroyableHits = 0;

            foreach (Map.CollisionInfo info in collisions)
            {
                if (info.type == Map.CollisionInfo.CollisionType.Solid)
                    ++numSolidHits;
                else if (info.type == Map.CollisionInfo.CollisionType.Safe)
                {
                    ++numSolidHits;
                    ++numSafeHits;
                }
                else if (info.type == Map.CollisionInfo.CollisionType.Destroyable)
                    ++numDestroyableHits;
            }

            if (numSolidHits > 0)
            {
                float damage = velocity.Length() / structuralStrength;
                if (damage > structuralHardness)
                    DamagedBy(owner, damage);

                float elasticity = 0.5f;
                velocity = velocity * -elasticity;
                position = lastPosition;
            }
            if (numSafeHits > 0)
            {
                touchingSafeZone = true;
            }
            else
            {
                touchingSafeZone = false;
            }
            if (numDestroyableHits > 0)
            {
                float damagePerBlockPlowed = 0.1f;
                float damage = (numDestroyableHits * damagePerBlockPlowed) / structuralStrength;
                DamagedBy(owner, damage);

                float viscosity = 0.9f;
                velocity = velocity * viscosity;

                Matrix myTransform =
                    Matrix.CreateTranslation(new Vector3(-Texture.Width / 2, -Texture.Height / 2, 0.0f)) *
                    Matrix.CreateRotationZ(Rotation) *
                    Matrix.CreateTranslation(new Vector3(Position, 0.0f));

                // Get the bounding rectangle of the person
                Rectangle mapRectangle = new Rectangle(0, 0, Texture.Width, Texture.Height);

                // The other object will always be smaller than the map, so we choose that one
                Resources.World.Map.DestroyTerrain(myTransform, Texture.Width, Texture.Height, TextureData);
            }
        }

        public override bool Touch(GameObject otherObject)
        {
            if (otherObject is Ship)
            {
                const float coefficientOfRestitution = 1.0f;

                Ship otherShip = otherObject as Ship;

                Vector2 normal = otherShip.Position - Position;
                normal.Normalize();

                float mySpeed = Vector2.Dot(Velocity, normal);
                float otherSpeed = Vector2.Dot(otherObject.Velocity, normal);

                float myNewSpeed = ((coefficientOfRestitution + 1) * otherShip.Mass * otherSpeed) + mySpeed * (Mass - coefficientOfRestitution * otherShip.Mass);
                myNewSpeed /= Mass + otherShip.Mass;

                float otherNewSpeed = ((coefficientOfRestitution + 1) * Mass * mySpeed) + otherSpeed * (otherShip.Mass - coefficientOfRestitution * Mass);
                otherNewSpeed /= Mass + otherShip.Mass;

                float damage = Math.Abs(myNewSpeed - mySpeed) / structuralStrength;
                if (damage > structuralHardness)
                    DamagedBy(otherShip.owner, damage);

                velocity += normal * (myNewSpeed - mySpeed);

                float otherDamage = Math.Abs(otherNewSpeed - otherSpeed) / otherShip.structuralStrength;
                if (otherDamage > otherShip.structuralHardness)
                    otherShip.DamagedBy(owner, otherDamage);

                otherShip.velocity += normal * (otherNewSpeed - otherSpeed);

                IgnoreUntilSeparated(otherShip);
                otherShip.IgnoreUntilSeparated(this);

                return true;
            }
            else
                return base.Touch(otherObject);
        }

        public void ReplenishMissiles()
        {
            numMissiles = maxMissiles;
        }

        private void AddThrustParticles(float thrust)
        {
            for (int i = 0; i < 2; ++i)
            {
                Vector2 shipPointing = new Vector2(
                    (float)(Math.Cos(rotation - MathHelper.PiOver2)),
                    (float)(Math.Sin(rotation - MathHelper.PiOver2)));
                Vector2 particlePosition = position - (shipPointing * (float)Texture.Height * 0.5f);
                Vector2 particleVelocity = shipPointing * thrust * enginepower * -10.0f;
                // Generate extra velocity in polar coordinates so we get a circular spread
                float extraVelocityAngle = (float)(Resources.Rand.NextDouble() * Math.PI);
                float extraVelocityMagnitude = ((float)Resources.Rand.NextDouble() - 0.5f) * 100.0f;
                particleVelocity += new Vector2((float)(Math.Cos(extraVelocityAngle) * extraVelocityMagnitude),
                                       (float)(Math.Sin(extraVelocityAngle) * extraVelocityMagnitude));
                Vector2 particleAcceleration = Vector2.Zero;
                Color particleColor = new Color();

                // Make reddy orange
                particleColor.R = (byte)(128 + Resources.Rand.Next(127));
                particleColor.G = (byte)Resources.Rand.Next(127);
                particleColor.B = 0;
                particleColor.A = 255;

                float particleScale = (float)Resources.Rand.NextDouble() * 16.0f * thrust;

                Resources.World.AddObject(new EffectObject(
                    "dot_8",
                    SpriteBlendMode.Additive,
                    particlePosition,
                    particleVelocity,
                    particleAcceleration,
                    particleColor,
                    particleScale,
                    -8.0f / 0.125f,
                    5));
            }
        }

        public void KilledBy(Player killer)
        {
            Resources.World.RemoveObject(this);
            Explode();
            owner.ShipDestroyed();
        }

        public void DamagedBy(Player damager, float damageDone)
        {
            if (currentShieldPoints - damageDone > 0.0f)
            {
                currentShieldPoints -= damageDone;
            }
            else
            {
                currentHullPoints -= (damageDone - currentShieldPoints);
                currentShieldPoints = 0.0f;
            }

            if (currentHullPoints <= 0.0f)
            {
                KilledBy(damager);
            }
        }

        private void Explode()
        {
            for (int i = 0; i < 50; ++i)
            {
                Vector2 particleVelocity = velocity * 0.1f;
                // Generate extra velocity in polar coordinates so we get a circular spread
                float extraVelocityAngle = (float)(Resources.Rand.NextDouble() * Math.PI);
                float extraVelocityMagnitude = ((float)Resources.Rand.NextDouble() - 0.5f) * 200.0f;
                particleVelocity += new Vector2((float)(Math.Cos(extraVelocityAngle) * extraVelocityMagnitude),
                                       (float)(Math.Sin(extraVelocityAngle) * extraVelocityMagnitude));
                Vector2 particleAcceleration = Resources.World.Gravity * 50.0f;
                Color particleColor = new Color();

                // Make reddy orange
                particleColor.R = (byte)(128 + Resources.Rand.Next(127));
                particleColor.G = (byte)Resources.Rand.Next(127);
                particleColor.B = 0;
                particleColor.A = 255;

                float particleScale = (float)Resources.Rand.NextDouble() * 16.0f;

                Resources.World.AddObject(new EffectObject(
                    "dot_8",
                    SpriteBlendMode.Additive,
                    position,
                    particleVelocity,
                    particleAcceleration,
                    particleColor,
                    particleScale,
                    -8.0f / 1.0f,
                    5));
            }
        }
    }
}